/* 
 * Copyright 2011 kOSMik.
 * This file is part of kOSMik.
 * 
 * kOSMik is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * kOSMik is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with kOSMik.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * kOSMik:
 * Open Street Map - Editor.
 * http://code.google.com/p/swp-dv-ws2010-osm-2/
 */

package de.fuberlin.kosmik.data.osmt;

import android.content.ContentValues;
import android.database.Cursor;

/**
 * This class encapsulate an row of the Feature table and defines read and write
 * operations.
 * 
 * @author Johannes
 */
public class OsmtFeature implements OsmtTable {

	/**
	 * Name of table Feature.
	 */
	static final String TABLE = "Feature";
	/**
	 * 1. Column name.
	 */
	static final String COLUMN_KEY_NAME = "Feature.key_name";
	/**
	 * 2. Column name.
	 */
	static final String COLUMN_VALUE_NAME = "Feature.value_name";
	/**
	 * 3. Column name.
	 */
	static final String COLUMN_POI = "Feature.poi";
	/**
	 * 4. Column name.
	 */
	static final String COLUMN_WAY = "Feature.way";
	/**
	 * 5. Column name.
	 */
	static final String COLUMN_AREA = "Feature.area";
	/**
	 * 6. Column name.
	 */
	static final String COLUMN_ICON = "Feature.icon_uri";
	/**
	 * 7. Column name.
	 */
	static final String COLUMN_WIKI = "Feature.wiki_uri";
	/**
	 * 8. Column name.
	 */
	static final String COLUMN_RENDER = "Feature.render_uri";
	/**
	 * 9. Column name.
	 */
	static final String COLUMN_PHOTO = "Feature.photo_uri";
	/**
	 * 10. Column name.
	 */
	static final String COLUMN_USE = "Feature.use_stat";
	private final OsmtDatabase osmtDatabase;
	private final OsmtData osmtData;
	private final OsmtFeatureRecord record = new OsmtFeatureRecord();
	private boolean hasJustBeenInserted;

	/**
	 * Constructor.
	 * <p/>
	 * Creates a new OsmtFeature object. At first, the constructor tries to load
	 * the dataset of this feature from the database. Therefore, it is
	 * necessary, that the primary keys of the Osmt Feature table are given. The
	 * primary keys are the arguments <code>key</code> and <code>value</code>.
	 * All other parameters can be <code>null</code>. If one of the optional
	 * parameters are <code>null</code>, in case of existance of this dataset,
	 * the null-parameter will be replaced by the loaded. But then, if a
	 * additional parameter is set (not null) and differs from the value of a
	 * field of the loaded table row, so the passed parameter value will
	 * override, that means update, the value in the database.
	 * 
	 * @param osmtData The connection to the Osmt database.
	 * @param key The key is part of the table primary key.
	 * @param value The value is part of the table primary key.
	 * @param isPoi Indicates whether the feature is intended to poi's.
	 * @param isWay Indicates whether the feature is intended to ways.
	 * @param isArea Indicates whether the feature is intended to areas.
	 * @param icon An url to an icon.
	 * @param wiki The url to the wiki page with description about the feature.
	 * @param render An url to an image, how this feature would be rendered.
	 * @param photo An url to a photo that shows the mapped object in reality.
	 * @throws OsmtObjectException if this object cannot be created. That can
	 *             happen, if the feature neither could read from the database
	 *             nor could written into.
	 */
	public OsmtFeature(final OsmtData osmtData, final String key, final String value,
			final Boolean isPoi, final Boolean isWay, final Boolean isArea, final String icon,
			final String wiki, final String render, final String photo)
			throws OsmtObjectException {
		super();
		this.osmtData = osmtData;
		this.osmtDatabase = ((OsmtDataImpl) osmtData).getDatabase();
		if (!set(key, value, isPoi, isWay, isArea, icon, wiki, render, photo)) {
			throw new OsmtObjectException(String.format(
					"can neither load nor create feature %s - %s.", key, value));
		}
	}

	/**
	 * Same as
	 * {@link #OsmtFeature(OsmtData, String, String, Boolean, Boolean, Boolean, String, String, String, String)}
	 * but with nulls for all the other fields
	 * 
	 * @param osmtData The connection to the Osmt database.
	 * @param key The key is part of the table primary key.
	 * @param value The value is part of the table primary key.
	 * @throws OsmtObjectException if this object cannot be created. That can
	 *             happen, if the feature neither could read from the database
	 *             nor could written into.
	 */
	public OsmtFeature(OsmtData osmtData, String key, String value) throws OsmtObjectException {
		this(osmtData, key, value, null, null, null, null, null, null, null);
	}

	/**
	 * Constructor.
	 * <p/>
	 * Behavior like
	 * {@link OsmtFeature#OsmtFeature(OsmtData, String, String, Boolean, Boolean, Boolean, String, String, String, String)}
	 * , but instead of loose parameters, you can pass all informations packed
	 * in a record.
	 * 
	 * @param osmtData The connection to the Osmt database.
	 * @param osmFeatureRecord The record containing all needed informations for
	 *            object construction.
	 * @throws OsmtObjectException if this object cannot be created.
	 */
	public OsmtFeature(OsmtData osmtData, OsmtFeatureRecord osmFeatureRecord)
			throws OsmtObjectException {
		this(osmtData, osmFeatureRecord.osmKey, osmFeatureRecord.osmValue, Boolean
				.valueOf(OsmtElementRecord.isNode(osmFeatureRecord)), Boolean
				.valueOf(OsmtElementRecord.isWay(osmFeatureRecord)), Boolean
				.valueOf(OsmtElementRecord.isArea(osmFeatureRecord)), osmFeatureRecord.icon,
				osmFeatureRecord.wiki, osmFeatureRecord.rendering, osmFeatureRecord.photo);
	}

	/**
	 * Sets the data for the feature without detailed testing. The data is not
	 * initially written to the database.
	 * 
	 * @param osmtFeatureRecord The record with all relevant data.
	 */
	void setTrusted(OsmtFeatureRecord osmtFeatureRecord) {
		record.osmKey = osmtFeatureRecord.osmKey;
		record.osmValue = osmtFeatureRecord.osmValue;
		record.elements = osmtFeatureRecord.elements;
		record.icon = osmtFeatureRecord.icon;
		record.wiki = osmtFeatureRecord.wiki;
		record.rendering = osmtFeatureRecord.rendering;
		record.photo = osmtFeatureRecord.photo;
		record.use = osmtFeatureRecord.use;
		hasJustBeenInserted = false;
	}

	/**
	 * Sets the data for the feature without detailed checking. The data is not
	 * initially written to the database. Same functionality like
	 * {@link OsmtFeature#setTrusted(OsmtFeatureRecord)}.
	 * 
	 * @param osmKey The key is part of the table primary key.
	 * @param osmValue The value is part of the table primary key.
	 * @param isPoi Indicates whether the feature is intended to poi's.
	 * @param isWay Indicates whether the feature is intended to ways.
	 * @param isArea Indicates whether the feature is intended to areas.
	 * @param icon An url to an icon.
	 * @param wiki The url to the wiki page with description about the feature.
	 * @param render An url to an image, how this feature would be rendered.
	 * @param photo An url to a photo that shows the mapped object in reality.
	 * @param use Specifies how often the feature has already been used.
	 */
	void setTrusted(String osmKey, String osmValue, boolean isPoi, boolean isWay,
			boolean isArea, String icon, String wiki, String render, String photo, int use) {
		record.osmKey = osmKey;
		record.osmValue = osmValue;
		record.elements = OsmtElementRecord.convertElementsToInt(isPoi, isWay, isArea);
		record.icon = icon;
		record.wiki = wiki;
		record.rendering = render;
		record.photo = photo;
		record.use = use;
		hasJustBeenInserted = false;
	}

	/**
	 * Set the data of this feature direct.
	 * <p/>
	 * At first, these method tries to load the dataset of this feature from the
	 * database. Therefore, it is necessary, that the primary keys of the Osmt
	 * Feature table are given. The primary keys are the arguments
	 * <code>key</code> and <code>value</code>. All other parameters can be
	 * <code>null</code>. If one of the optional parameters are
	 * <code>null</code>, in case of existance of this dataset, the
	 * null-parameter will be replaced by the loaded. But then, if a additional
	 * parameter is set (not null) and differs from the value of a field of the
	 * loaded table row, so the passed parameter value will override, that means
	 * update, the value in the database.
	 * 
	 * @param osmKey The key is part of the table primary key.
	 * @param osmValue The value is part of the table primary key.
	 * @param isPoi Indicates whether the feature is intended to poi's.
	 * @param isWay Indicates whether the feature is intended to ways.
	 * @param isArea Indicates whether the feature is intended to areas.
	 * @param icon An url to an icon.
	 * @param wiki The url to the wiki page with description about the feature.
	 * @param rendering An url to an image, how this feature would be rendered.
	 * @param photo An url to a photo that shows the mapped object in reality.
	 * @return True if the feature was loaded and could be updated as necessary.
	 *         Is also true if instead the feature as a new line could be
	 *         inserted into the database. Otherwise false.
	 */
	public boolean set(String osmKey, String osmValue, Boolean isPoi, Boolean isWay,
			Boolean isArea, String icon, String wiki, String rendering, String photo) {
		if (loadFeatureByKeyValue(this, osmKey, osmValue)) {
			OsmtFeature.updateFeature(this, isPoi, isWay, isArea, icon, wiki, rendering, photo);
			return true;
		}
		record.osmKey = osmKey;
		record.osmValue = osmValue;
		record.elements = OsmtElementRecord.convertElementsToInt(
				isPoi != null && isPoi.booleanValue(), isWay != null && isWay.booleanValue(),
				isArea != null && isArea.booleanValue());
		record.icon = icon;
		record.wiki = wiki;
		record.rendering = rendering;
		record.photo = photo;
		return tryInsertAsNewFeature(this);
	}

	/**
	 * Set the data of this feature direct. In addition, this method behaves
	 * equivalent to
	 * {@link #set(String, String, Boolean, Boolean, Boolean, String, String, String, String)}
	 * 
	 * @param osmtFeatureRecord The record with all relevant data.
	 * @return True if the feature was loaded and could be updated as necessary.
	 *         Is also true if instead the feature as a new line could be
	 *         inserted into the database. Otherwise false.
	 */
	public boolean set(OsmtFeatureRecord osmtFeatureRecord) {
		return set(osmtFeatureRecord.osmKey, osmtFeatureRecord.osmValue,
				Boolean.valueOf(OsmtElementRecord.isNode(osmtFeatureRecord)),
				Boolean.valueOf(OsmtElementRecord.isWay(osmtFeatureRecord)),
				Boolean.valueOf(OsmtElementRecord.isArea(osmtFeatureRecord)),
				osmtFeatureRecord.icon, osmtFeatureRecord.wiki, osmtFeatureRecord.rendering,
				osmtFeatureRecord.photo);
	}

	/**
	 * Returns a record containing all feature data
	 * 
	 * @return a OsmtFeatureRecord-record
	 */
	public OsmtFeatureRecord get() {
		return record;
	}

	private static void updateFeature(final OsmtFeature feature, final Boolean isPoi,
			final Boolean isWay, final Boolean isArea, final String icon, final String wiki,
			final String rendering, final String photo) {

		final ContentValues contentValues = new ContentValues();

		if (isPoi != null
				&& isPoi.equals(Boolean.valueOf(OsmtElementRecord.isNode(feature.record)))) {
			contentValues.put(COLUMN_POI, Integer.valueOf(isPoi.booleanValue() ? 1 : 0));
		}

		if (isWay != null
				&& isWay.equals(Boolean.valueOf(OsmtElementRecord.isWay(feature.record)))) {
			contentValues.put(COLUMN_WAY, Integer.valueOf(isWay.booleanValue() ? 1 : 0));
		}

		if (isArea != null
				&& isArea.equals(Boolean.valueOf(OsmtElementRecord.isArea(feature.record)))) {
			contentValues.put(COLUMN_AREA, Integer.valueOf(isArea.booleanValue() ? 1 : 0));
		}

		if (icon != null && !icon.equals(feature.record.icon)) {
			contentValues.put(COLUMN_ICON, icon);
		}
		if (wiki != null && !wiki.equals(feature.record.wiki)) {
			contentValues.put(COLUMN_WIKI, wiki);
		}
		if (rendering != null && !rendering.equals(feature.record.rendering)) {
			contentValues.put(COLUMN_RENDER, rendering);
		}
		if (photo != null && !photo.equals(feature.record.photo)) {
			contentValues.put(COLUMN_PHOTO, photo);
		}

		if (contentValues.size() > 0) {
			final String whereClause = String.format("%s='%s' AND %s='%s'", COLUMN_KEY_NAME,
					feature.record.osmKey, COLUMN_VALUE_NAME, feature.record.osmValue);
			feature.osmtDatabase.update(feature, contentValues, whereClause, null);
		}

	}

	private static boolean loadFeatureByKeyValue(final OsmtFeature osmtFeature,
			final String osmKey, final String osmValue) {

		final String lookupByKeyValue = String.format("%s='%s' AND %s='%s' ", COLUMN_KEY_NAME,
				osmKey, COLUMN_VALUE_NAME, osmValue);
		final String[] selectionCols = new String[] { COLUMN_POI, COLUMN_WAY, COLUMN_AREA,
				COLUMN_ICON, COLUMN_WIKI, COLUMN_RENDER, COLUMN_PHOTO, COLUMN_USE };
		final OsmtQuery query = new OsmtQuery(TABLE, selectionCols, lookupByKeyValue);
		final boolean featureFound = osmtFeature.osmtDatabase.query(query,
				new OnObtainCursorListener() {

					@Override
					public boolean onObtainCursor(Cursor cursor) {
						if (cursor.moveToFirst()) {
							osmtFeature.setTrusted(osmKey, osmValue, cursor.getInt(0) != 0,
									cursor.getInt(1) != 0, cursor.getInt(2) != 0,
									cursor.getString(3), cursor.getString(4),
									cursor.getString(5), cursor.getString(6), cursor.getInt(7));
							return true;
						}
						return false;
					}
				});
		// load language dependent data
		if (featureFound) {
			final String bestFittingComment = OsmtFeatureLangText
					.getBestFittingFeatureCommentByKeyValue(osmtFeature.osmtData, osmKey,
							osmValue, null);
			osmtFeature.record.comment = bestFittingComment;
		}

		return featureFound;
	}

	private static boolean tryInsertAsNewFeature(final OsmtFeature feature) {
		final ContentValues contentValues = new ContentValues();
		contentValues.put(COLUMN_KEY_NAME, feature.record.osmKey);
		contentValues.put(COLUMN_VALUE_NAME, feature.record.osmValue);
		contentValues
				.put(COLUMN_POI, Boolean.valueOf(OsmtElementRecord.isNode(feature.record)));
		contentValues.put(COLUMN_WAY, Boolean.valueOf(OsmtElementRecord.isWay(feature.record)));
		contentValues.put(COLUMN_AREA,
				Boolean.valueOf(OsmtElementRecord.isArea(feature.record)));
		contentValues.put(COLUMN_ICON, feature.record.icon);
		contentValues.put(COLUMN_WIKI, feature.record.wiki);
		contentValues.put(COLUMN_RENDER, feature.record.rendering);
		contentValues.put(COLUMN_PHOTO, feature.record.photo);
		contentValues.put(COLUMN_USE, Integer.valueOf(0));
		feature.hasJustBeenInserted = feature.osmtDatabase.insertRecord(feature, contentValues);
		return feature.hasJustBeenInserted;
	}

	/**
	 * Use this method to signalize the statistic, that this osm element was
	 * used by a user one more time.
	 * 
	 * @param feature which use counter should be increment.
	 */
	public static void useOneMoreTime(final OsmtFeature feature) {
		final ContentValues contentValues = new ContentValues();
		final String lookupByKeyValue = String.format("%s='%s' AND %s='%s' ", COLUMN_KEY_NAME,
				feature.record.osmKey, COLUMN_VALUE_NAME, feature.record.osmValue);
		contentValues.put(COLUMN_USE, Integer.valueOf(++feature.record.use));
		feature.osmtDatabase.update(feature, contentValues, lookupByKeyValue, null);
	}

	@Override
	public String getTableName() {
		return TABLE;
	}

	/**
	 * Was the feature already in the database and just loaded, or does it not
	 * exists prior.
	 * 
	 * @return true, if the feature was newly inserted into database.
	 */
	public boolean hasJustBeenInserted() {
		return hasJustBeenInserted;
	}
}
